package data

import (
	"context"
	"time"

	"github.com/go-kratos/kratos/v2/log"
	"github.com/google/wire"
	"github.com/redis/go-redis/v9"
	"github.com/uptrace/opentelemetry-go-extra/otelgorm"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/plugin/dbresolver"

	"gitee.com/snitso/micro/app/evaluation/internal/biz/transaction"
	"gitee.com/snitso/micro/app/evaluation/internal/conf"
	"gitee.com/snitso/micro/pkg/logx"
	"gitee.com/snitso/micro/pkg/sessions"
)

// ProviderSet is data providers.
var ProviderSet = wire.NewSet(NewData, NewTransaction)

type TxDB struct {
	*gorm.DB
}

// Data .
type Data struct {
	DB    *TxDB
	RDB   *redis.Client
	Store *sessions.RedisStore
}

type contextTxKey struct{}

// NewTransaction .
func NewTransaction(d *Data) transaction.Transaction {
	return d.DB
}

func (d *TxDB) InTx(ctx context.Context, fn func(ctx context.Context) error) error {
	return d.TxDB(ctx).WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		ctx = context.WithValue(ctx, contextTxKey{}, tx)
		return fn(ctx)
	})
}

func (d *TxDB) TxDB(ctx context.Context) *gorm.DB {
	tx, ok := ctx.Value(contextTxKey{}).(*gorm.DB)
	if ok {
		return tx
	}
	return d.DB
}

// NewData .
func NewData(c *conf.Data, logger log.Logger) (*Data, func(), error) {
	l := log.NewHelper(log.With(logger, "module", "data"))

	db, err := newDB(c.Database, l)
	if err != nil {
		panic(err)
	}

	rdb := newRDB(c.Redis)
	store := newSessionStore(rdb, l)
	data := &Data{
		DB:    &TxDB{db},
		RDB:   rdb,
		Store: store,
	}

	cleanup := func() {
		err := rdb.Close()
		if err != nil {
			l.Error("close redis error", err)
		}
		l.Info("closing the data resources")
	}
	return data, cleanup, nil
}

// newDB 實例化
// windows 下连接方式：127.0.0.1:3305/3306
func newDB(c *conf.Data_Database, logger *log.Helper) (*gorm.DB, error) {
	db, err := gorm.Open(mysql.New(mysql.Config{
		DSN: c.Source,
	}), &gorm.Config{
		DisableForeignKeyConstraintWhenMigrating: true,
		Logger: logx.NewGormLogger(logger, logx.Config{
			SlowThreshold:             200 * time.Millisecond,
			LogLevel:                  logx.Info,
			IgnoreRecordNotFoundError: true,
			Colorful:                  true,
		}),
	})

	if err != nil {
		logger.Fatalf("failed opening connection to mysql: %v", err)
		return nil, err
	}

	err = db.Use(dbresolver.Register(dbresolver.Config{
		Sources:  []gorm.Dialector{mysql.Open(c.Source)},
		Replicas: []gorm.Dialector{mysql.Open(c.Source)},
	}))

	if err = db.Use(otelgorm.NewPlugin()); err != nil {
		logger.Error("gorm tracing error", err)
	}

	return db, err
}

// NewRDB 实例化 redis 客户端
func newRDB(c *conf.Data_Redis) *redis.Client {
	// 如果连接 docker 中的 redis
	// 1. 需要将地址改为 redis:6379
	// 2. redis.conf 中的 bind 需要注释掉
	// 3. protected-mode 改为 no
	// 4. 应用和 redis 容器需要在同一个网络中
	// 5. windows 连接：127.0.0.1:6378
	rdb := redis.NewClient(&redis.Options{
		Addr:         c.Addr,
		WriteTimeout: c.WriteTimeout.AsDuration(),
		ReadTimeout:  c.ReadTimeout.AsDuration(),
	})

	return rdb
}

// newSessionStore 实例化 session store
func newSessionStore(rdb *redis.Client, logger *log.Helper) *sessions.RedisStore {
	sessionStore, err := sessions.NewRedisStore(rdb, []byte("secret"))
	sessionStore.SetMaxAge(24 * 3600 * 30)
	if err != nil {
		logger.Error("new session store error", err)
	}
	return sessionStore
}
